using System;
using System.Drawing;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;

//include the goblin base and the truevision engine
using GoblinV2.Device.Camera;
using GoblinV2.Device.Marker;
using GoblinV2;
using GoblinV2.GUI.GUI2D;
using GoblinV2.GUI.Events;
using TrueVision3D;
using DxVBLibA;

namespace Goblin2
{
    public partial class Manhattanville : System.Windows.Forms.Form
    {
        #region Class Variables

        // TV Classes
        private TVEngine TV;
        public TVSceneClass m_kScenegraph;
        public TVTextureFactory m_kTexFactory;
        public TVInputEngineClass inputEngine;
        public TVGlobalsClass m_kGlobals;
        private TVLightEngine lightEngine;
        private TVScreen2DImmediate scr2D;
        private TVScreen2DText scrText;
        public TVMathLibrary mathLib;
        private VideoCapture m_kVideo;
        private MarkerTracker m_kTracker;

        // Geometrical objects and data
        private TVMesh xAxis, yAxis, zAxis, origin, originBig, pointer, ray, marker;
        public List<TVMesh> buildings, buildingsBig;
        private TrackedObject originTO, originBigTO, pointerTO, rayTO, markerTO;
        public List<TrackedObject> objects;
        private D3DLIGHT8 myD3DLight8;
        private D3DMATRIX lightMat;
        public Hashtable buildingInfo, buildingHash, buildingBigHash;
        public ArrayList rules;
        private Stats stats;
        // Currently selected building index
        public int bid = 0;

        // Main loop controller
        private bool m_bDoLoop = true;

        #endregion

        private void Manhattanville_Load(object sender, System.EventArgs e)
        {
            // Initialize TV3D objects
            TV = new TVEngine();
            m_kScenegraph = new TVSceneClass();
            m_kTexFactory = new TVTextureFactory();
            m_kGlobals = new TVGlobalsClass();
            mathLib = new TVMathLibrary();
            inputEngine = new TVInputEngineClass();
            scr2D = new TVScreen2DImmediate();
            scrText = new TVScreen2DText();
            lightEngine = new TVLightEngine();

            // Other initialization 
            TV.Init3DWindowedMode(this.Handle.ToInt32(), true);
            TV.DisplayFPS = true;
            buildingHash = new Hashtable();
            buildingBigHash = new Hashtable();
            objects = new List<TrackedObject>();
            loadBuildingInfo();
            this.Show();
            this.Focus();

            // Load the rule textures
            Rule.dottedTexture = m_kTexFactory.LoadTexture("Textures/dotted.jpg", "DottedTexture", -1, -1, CONST_TV_COLORKEY.TV_COLORKEY_NO, true, true);
            Rule.stripedTexture = m_kTexFactory.LoadTexture("Textures/striped.jpg", "StripedTexture", -1, -1, CONST_TV_COLORKEY.TV_COLORKEY_NO, true, true);
            Rule.checkeredTexture = m_kTexFactory.LoadTexture("Textures/checkered.jpg", "CheckeredTexture", -1, -1, CONST_TV_COLORKEY.TV_COLORKEY_NO, true, true);

            // Set app base as using the camera, start the tracker, etc.
            MarkerBase.Base.InitModules();
            m_kVideo = MarkerBase.Base.GetVideoCapture();
            m_kTracker = MarkerBase.Base.GetTracker();
            m_kVideo.InitVideoCapture(-1, VideoCapture.RESOLUTION_640x480, false, GoblinEnums.CameraLibraryType.DirectShow);
            m_kVideo.InitCamera(0, -1);
            MarkerBase.Base.SetUsingCamera(true);
            m_kTracker.InitTracker(781.761f, 781.551f, "manhattanville.cf");
            //m_kTracker.InitTracker(410f, 410f, "manhattanville.cf");

            xAxis = m_kScenegraph.CreateMeshBuilder("xAxis");
            xAxis.CreateCylinder(0.05f, 100f, 20, true);
            xAxis.SetColor(m_kGlobals.RGBA(1, 0, 0f, 1f), true);
            yAxis = xAxis.DuplicateMesh("yAxis");
            yAxis.SetColor(m_kGlobals.RGBA(0, 1, 0f, 1f), true);
            zAxis = xAxis.DuplicateMesh("zAxis");
            zAxis.SetColor(m_kGlobals.RGBA(0, 0, 1f, 1f), true);
            xAxis.RotateZ((float)mathLib.PI_ / 2.0f, true);
            zAxis.RotateX((float)mathLib.PI_ / 2.0f, true);

            origin = m_kScenegraph.CreateMeshBuilder("Origin");
            origin.CreateBox(0.1f, 0.1f, 0.1f, false);
            origin.AddChild(ref xAxis); origin.AddChild(ref yAxis); origin.AddChild(ref zAxis);
            originTO = new TrackedObject(origin, m_kTracker.SetMarkerArray("manhattanville"));
            objects.Add(originTO);

            // Duplicate stuff for the big poster
            originBig = origin.DuplicateMesh("OriginBig");
            TVMesh xAxisBig = xAxis.DuplicateMesh("xAxisBig"), yAxisBig = yAxis.DuplicateMesh("yAxisBig"), zAxisBig = zAxis.DuplicateMesh("zAxisBig");
            originBig.AddChild(ref xAxisBig); originBig.AddChild(ref yAxisBig); originBig.AddChild(ref zAxisBig);
            originBig.RotateY((float)mathLib.PI_ / 2.0f, true);
            originBig.ForceMatrixUpdate();
            originBigTO = new TrackedObject(originBig, m_kTracker.SetMarkerArray("Big_Poster_4_Markers"));
            objects.Add(originBigTO);
            // Set the big poster scene's scale
            originBigTO.scaleTransAdjst.m11 = originBigTO.scaleTransAdjst.m22 = originBigTO.scaleTransAdjst.m33 = 5;
            originBigTO.updateMatrix();

            // Add the pointer to toolbar1
            pointer = m_kScenegraph.CreateMeshBuilder("Pointer");
            pointer.CreateSphere(0, .5f, 6, 6, Color.Red.ToArgb());
            pointer.SetPosition(-1f, 4f, 0f);
            pointer.ComputeBoundingVolumes();
            pointerTO = new TrackedObject(pointer, m_kTracker.SetMarkerArray("toolbar1"));
            objects.Add(pointerTO);

            // Add ray to toolbar2
            ray = m_kScenegraph.CreateMeshBuilder("Ray");
            ray.CreateBox(0.1f, 0.1f, 10f, false);
            float x3D = 0.1f * Rule.SCALE - ray.GetPosition().y - 7.1f;
            float y3D = 0.1f * Rule.SCALE - ray.GetPosition().x - .25f;
            ray.SetRotation(0f, 0f, (float)(mathLib.PI_ / 2));
            ray.SetPosition(y3D + .3f, x3D, -.3f);
            ray.RotateY(90, false);
            ray.ForceMatrixUpdate();
            ray.ComputeBoundingVolumes();
            ray.SetColor(Color.SkyBlue.ToArgb(), false);
            rayTO = new TrackedObject(ray, m_kTracker.SetMarkerArray("toolbar2"));
            objects.Add(rayTO);

            // Add the marker symbol to toolbar31
            int markerTexture = m_kTexFactory.LoadTexture("marker_texture.jpg", "MarkerTexture", -1, -1, CONST_TV_COLORKEY.TV_COLORKEY_NO, true, true);
            marker = (TVMeshClass)m_kScenegraph.CreateMeshBuilder("Marker");
            marker.AddWall(markerTexture, .001f, 0, .001f, -2, 2, -1, 1, 1, true, false);
            marker.AddWall(0, 0, 0, 0, -2, 2, -1, 1, 1, true, false);
            marker.SetColor(Color.Brown .ToArgb(), false);
            markerTO = new TrackedObject(marker, m_kTracker.SetMarkerArray("toolbar31"));
            objects.Add(markerTO);

            // Terain generation
            float factor = -9.38f / 1353;
            GenerateTerrain(factor);
            LoadBuildings(factor);

            // Light settings
            myD3DLight8 = new D3DLIGHT8();
            myD3DLight8.Type = CONST_D3DLIGHTTYPE.D3DLIGHT_POINT;
            myD3DLight8.Position = m_kGlobals.Vector(198.0000f, 79.0000f, -58.0000f);
            myD3DLight8.ambient = m_kGlobals.DXColor(0.0000f, 0.0000f, 0.0000f, 1.0000f);
            myD3DLight8.diffuse = m_kGlobals.DXColor(0.6000f, 0.6000f, 0.6000f, 1.0000f);
            myD3DLight8.specular = m_kGlobals.DXColor(0.2000f, 0.2000f, 0.2000f, 1.0000f);
            myD3DLight8.Range = 4140.0000f;
            myD3DLight8.Attenuation0 = 0.5f;
            myD3DLight8.Falloff = 1;
            myD3DLight8.Phi = 3.14f / 4.0f;
            myD3DLight8.Theta = myD3DLight8.Phi / 2;
            lightEngine.CreateLight(ref myD3DLight8, "name", false);
            lightMat = new D3DMATRIX();
            mathLib.TVMatrixIdentity(ref lightMat);
            lightMat.m41 = 198; lightMat.m42 = 79; lightMat.m43 = -58;

            // Create a bunch of rules
            rules = new ArrayList();
            rules.Add(new Rule(this, m_kTracker.SetMarkerArray("toolbar16"), 10, 10, Rule.ruleType.COLOR));
            rules.Add(new Rule(this, m_kTracker.SetMarkerArray("toolbar17"), 10, 10, Rule.ruleType.PATTERN));
            rules.Add(new Rule(this, m_kTracker.SetMarkerArray("toolbar18"), 10, 10, Rule.ruleType.COLOR));
            rules.Add(new Rule(this, m_kTracker.SetMarkerArray("toolbar19"), 10, 10, Rule.ruleType.PATTERN));
            rules.Add(new Rule(this, m_kTracker.SetMarkerArray("toolbar20"), 10, 10, Rule.ruleType.COLOR));
            rules.Add(new Rule(this, m_kTracker.SetMarkerArray("toolbar21"), 10, 10, Rule.ruleType.PATTERN));
            rules.Add(new Rule(this, m_kTracker.SetMarkerArray("toolbar22"), 10, 10, Rule.ruleType.COLOR));
            rules.Add(new Rule(this, m_kTracker.SetMarkerArray("toolbar23"), 10, 10, Rule.ruleType.PATTERN));

            MarkerBase.Base.InitRendering(m_kScenegraph);

            // Start our main engine loop up
            Main_Loop();
        }

        private void Main_Loop()
        {
            D3DMATRIX tmp = new D3DMATRIX(), d3dmat;
            DateTime lastClick = DateTime.Now;
            bool m_bShowGoblin = false, m_bShowGoblinToggledLast = false;
            stats = new Stats(this, m_kTracker.SetMarkerArray("toolbar7"), m_kTracker.SetMarkerArray("toolbar6"), 
                m_kTracker.SetMarkerArray("toolbar3"), m_kTracker.SetMarkerArray("toolbar4"), m_kTracker.SetMarkerArray("toolbar5"), 10, 10, buildingInfo);
            
            // Keep looping until we are done here
            while (m_bDoLoop == true && !inputEngine.IsKeyPressed(CONST_TV_KEY.TV_KEY_ESCAPE))
            {
                // Clear the screen
                TV.Clear(false);

                // Render stuff here (only if we can)
                if (m_kVideo != null && m_kVideo.IsInitialized() && m_kTracker != null)
                {
                    // Look for some markers
                    Bitmap kBitmap = m_kTracker.FindMarkers();
                    MarkerBase.Base.UpdateRendering(kBitmap);

                    // Check if we found a marker
                    for (int i = 0; i < objects.Count; i++)
                    {
                        if (m_kTracker.IsMarkerFound(objects[i].artag_object_id))
                        {
                            // Get the marker matrix
                            d3dmat = m_kTracker.GetMarkerLHSMatrix();
                            // Transform the object based on it's adjustment matrix
                            mathLib.TVMatrixMultiply(ref d3dmat, ref objects[i].adjst, ref d3dmat);
                            ((TVMesh)objects[i].obj).SetMatrix(ref d3dmat);
                        }
                        else ((TVMesh)objects[i].obj).SetMatrix(ref tmp);

                        ((TVMesh)objects[i].obj).ForceMatrixUpdate();
                    }

                    // If sufficient time has passed since the last click (1 Tick = 100 nanoseconds) and the pointer was found
                    if (DateTime.Now.Ticks - lastClick.Ticks > 10000000 && m_kTracker.IsMarkerFound(pointerTO.artag_object_id))
                    {
                        // Iterate over rules
                        foreach (Rule r in rules)
                            foreach (UIChooser c in r.uichoosers)
                            {
                                // If the pointer intersects a chooser, iterate the chooser
                                if (pointer.TestBoxCollideWith(ref c.boxMesh))
                                {
                                    c.ActionPerformed(new ActionEvent(pointer));
                                    lastClick = DateTime.Now;
                                }
                            }
                    }
                }

                if (m_kTracker.IsMarkerFound(originTO.artag_object_id))
                {
                    // Attach the light position to the base
                    d3dmat = m_kTracker.GetMarkerLHSMatrix();
                    mathLib.TVMatrixMultiply(ref d3dmat, ref lightMat, ref d3dmat);
                    myD3DLight8.Position = m_kGlobals.Vector(d3dmat.m41, d3dmat.m42, d3dmat.m43);

                    // WIM poster positioning system
                    if (m_kTracker.IsMarkerFound(markerTO.artag_object_id))
                    {
                        D3DMATRIX originMat = origin.GetMatrix(), markerMat = marker.GetMatrix();
                        D3DVECTOR originMarker = new D3DVECTOR(), originX = new D3DVECTOR(), markerX = new D3DVECTOR();
                        originMarker.x = markerMat.m41 - originMat.m41;
                        originMarker.y = markerMat.m42 - originMat.m42;
                        //originMarker.z = markerMat.m43 - originMat.m43;
                        //Console.WriteLine("origin->marker: ( " + originMarker.x + ", " + originMarker.y + ", " + originMarker.z + " )");
                        originX.x = 1; originX.y = originX.z = 0;
                        markerX.x = 1; markerX.y = markerX.z = 0;
                        mathLib.TVVec3TransformNormal(ref originX, ref originX, ref originMat);
                        mathLib.TVVec3TransformNormal(ref markerX, ref markerX, ref markerMat);
                        mathLib.TVVec3Normalize(ref originX, ref originX);
                        mathLib.TVVec3Normalize(ref markerX, ref markerX);
                        //Console.WriteLine("origin->marker angle: " + 180.0 / mathLib.PI_ * mathLib.ACos(mathLib.TVVec3Dot(ref originX, ref markerX)));

                        // Set the big poster's origin to match that of the small setup
                        float scale = originBigTO.scaleTransAdjst.m11 / originTO.scaleTransAdjst.m11;
                        originBigTO.scaleTransAdjst.m42 = scale * -originMarker.x; originBigTO.scaleTransAdjst.m43 = scale * -originMarker.y;
                        mathLib.TVMatrixRotationX(ref originBigTO.rotateAdjst, mathLib.ACos(mathLib.TVVec3Dot(ref originX, ref markerX)));
                        // Update the big poster origin's adjustment matrix
                        originBigTO.updateMatrix();
                    }
                }

                // Handle Goblin toggling
                if (!inputEngine.IsKeyPressed(CONST_TV_KEY.TV_KEY_LEFTCONTROL) && !inputEngine.IsKeyPressed(CONST_TV_KEY.TV_KEY_RCONTROL))
                    m_bShowGoblinToggledLast = false;
                else if (!m_bShowGoblinToggledLast)
                {
                    m_bShowGoblin = !m_bShowGoblin;
                    m_bShowGoblinToggledLast = true;
                }

                // Building Selection
                for (int i = 0; i < buildings.Count; i++)
                {
                    //temp1 = ray.GetPosition();
                    //temp1.z += 10;
                    //temp2 = buildings[i].GetPosition();
                    if (buildings[i].TestBoxCollideWith(ref ray))
                    //if (buildings[i].AdvancedCollision(ref temp1, ref temp2, ref result, CONST_TV_TESTTYPE.TV_TESTTYPE_ACCURATETESTING))
                    {
                        //draw.DRAW_Line3D(temp1.x, temp1.y, temp1.z, temp2.x, -temp2.y, temp2.z, Color.Blue.ToArgb(), Color.Beige.ToArgb());
                        setSelectedBuilding(i);
                        //break;
                    }
                }

                // Render the meshes and goblin rules
                m_kScenegraph.RenderAllMeshes(true);
                if (m_bShowGoblin)
                    foreach (Rule r in rules)
                        r.RenderWidget(scr2D, scrText);

                // Display everything we have rendered
                TV.RenderToScreen();

                // Handle IO stuff
                Application.DoEvents();
            }
            Main_Quit();
        }

        // Set the currently selected building
        private void setSelectedBuilding(int b)
        {
            // TODO: re-evaluate the old selected building according to the rules to set it's color
            buildings[bid].SetColor(Color.White.ToArgb(), true);
            // Circular loop over building numbers
            bid = (b + buildings.Count) % buildings.Count;
            buildings[bid].SetColor(Color.Yellow.ToArgb(), true);

            // Update the stat sheet
            if (stats != null)
                stats.clearStats();
            foreach (object o in buildingHash.Keys)
            {
                if (buildingHash[o].Equals(buildings[bid]) && ((Hashtable)buildingInfo["DATA"]).ContainsKey(o))
                {
                    stats.clearStats();
                    stats.setStats(Stats.HT2List(((Hashtable)(((Hashtable)((Hashtable)buildingInfo["DATA"]))[o]))));
                }
            }
        }

        // Mike's scene transformation through arrow keys
        private void keyPress(object sender, KeyPressEventArgs e)
        {
            // Shift key reverses operation, prepare for potential rotation
            float multiplier = Char.IsLower(e.KeyChar) ? 1 : -1;
            e.KeyChar = Char.ToLower(e.KeyChar);
            D3DMATRIX rot = new D3DMATRIX();
            mathLib.TVMatrixRotationX(ref rot, (float)(mathLib.PI_ * .025 * multiplier));

            // Alt key to slow transformations, Handle translation / scaling / rotation
            if (inputEngine.IsKeyPressed(CONST_TV_KEY.TV_KEY_ALT_LEFT) || inputEngine.IsKeyPressed(CONST_TV_KEY.TV_KEY_ALT_RIGHT)) multiplier *= .1f;
            else if (e.KeyChar.Equals('q')) originBigTO.scaleTransAdjst.m43 += 1 * multiplier;
            else if (e.KeyChar.Equals('w')) originBigTO.scaleTransAdjst.m42 -= 1 * multiplier;
            else if (e.KeyChar.Equals('e')) originBigTO.scaleTransAdjst.m41 -= 1 * multiplier;
            else if (e.KeyChar.Equals('a')) originBigTO.scaleTransAdjst.m11 = originBigTO.scaleTransAdjst.m22 = originBigTO.scaleTransAdjst.m33 += .5f * multiplier;
            else if (e.KeyChar.Equals('s')) mathLib.TVMatrixMultiply(ref originBigTO.rotateAdjst, ref originBigTO.rotateAdjst, ref rot);
            else if (e.KeyChar.Equals('d')) setSelectedBuilding( bid + (int)multiplier );

            // Handle reset (to baseAdjst)
            else if (e.KeyChar.Equals('z'))
            {
                setSelectedBuilding(0);
                mathLib.TVMatrixIdentity(ref originBigTO.scaleTransAdjst);
                mathLib.TVMatrixIdentity(ref originBigTO.rotateAdjst);
            }

            // Update the big poster origin's adjustment matrix
            originBigTO.updateMatrix();

            // Depricated Mouse interaction code (doesn't work)
            //// Handle mouse input with 3D Rules
            //TVCollisionResult collisionResult = null;
            //// Get the movement of the mouse.
            //int x = 0, y = 0;
            //short b1 = 0, b2 = 0, b3 = 0;
            //inputEngine.GetAbsMouseState(ref x, ref y, ref b1, ref b2, ref b3);
            //// If we clicked on the mouse button...
            //if (inputEngine.IsKeyPressed(CONST_TV_KEY.TV_KEY_1))
            //{
            //    // Check if the mouse pointer has collided with an object
            //    collisionResult = m_kScenegraph.MousePicking(x, y, CONST_TV_OBJECT_TYPE.TV_COLLIDE_MESH, CONST_TV_TESTTYPE.TV_TESTTYPE_BOUNDINGBOX);
            //    //collisionResult = m_kScenegraph.MousePicking(((MouseEventArgs)e).X, ((MouseEventArgs)e).Y, CONST_TV_OBJECT_TYPE.TV_COLLIDE_MESH, CONST_TV_TESTTYPE.TV_TESTTYPE_BOUNDINGBOX);

            //    if (collisionResult.GetCollisionMesh() != null)
            //        Console.WriteLine(collisionResult.GetCollisionMesh().GetMeshName() + ": " + x + ", " + y);
            //    else
            //        Console.WriteLine("No Collision");

            //    // Check if we did have a collision, and increment an intersecting chooser
            //    if (collisionResult.IsCollision())
            //        foreach (Rule r in rules)
            //            foreach (UIChooser c in r.uichoosers)
            //                if (c.boxMesh == collisionResult.GetCollisionMesh() || c.myLabel.txtMesh == collisionResult.GetCollisionMesh())
            //                    c.ActionPerformed(new ActionEvent(this));
            //}
        }
    }

    // The View's modified TrackedObject supporting class
    public class TrackedObject
    {
        public Object obj;
        public int artag_object_id;
        public D3DMATRIX baseAdjst, scaleTransAdjst, rotateAdjst, adjst;
        public static TVMathLibrary mathLib = new TVMathLibrary();

        public TrackedObject(Object obj, int artag_object_id)
        {
            this.obj = obj;
            this.artag_object_id = artag_object_id;
            this.baseAdjst = ((TVMesh)obj).GetMatrix();
            this.scaleTransAdjst = new D3DMATRIX();
            this.rotateAdjst = new D3DMATRIX();
            this.adjst = new D3DMATRIX();
            mathLib.TVMatrixIdentity( ref this.scaleTransAdjst );
            mathLib.TVMatrixIdentity( ref this.rotateAdjst );
            mathLib.TVMatrixIdentity( ref this.adjst );
            mathLib.TVMatrixMultiply( ref adjst, ref baseAdjst, ref adjst );
        }

        public void updateMatrix()
        {
            mathLib.TVMatrixMultiply(ref adjst, ref baseAdjst, ref rotateAdjst);
            mathLib.TVMatrixMultiply(ref adjst, ref adjst, ref scaleTransAdjst);
        }
    }
}
